﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Drawing.Printing;
using System.Resources;
using System.Reflection;
using System.Runtime.InteropServices;

namespace Diagrams
{
    public partial class DiagramBox : UserControl
    {
        //диаграмма
        Diagram diagram;
        //память для Undo/Redo
        public DiagramMemento memento = new DiagramMemento();
        //выделенные фигуры
        List<Figure> selectedFigures = new List<Figure>();
        //маркеры
        List<Marker> markers = new List<Marker>();

        //фигура или маркер, который тащится мышью
        Figure draggedFigure = null;
        //определяет, нажата ли клавиша Shift
        bool ShiftPressed = false;
        //рамка выделения
        RectangleF selectionFrame = new RectangleF();
        //границы диаграммы
        RectangleF diagramBorder = new RectangleF();
        //флаг выделения рамкой
        bool IsSelectionWithFrame = false;
        //начальная точка выделения рамкой
        PointF startSelectionPoint = new PointF();
        //карандаш отрисовки прямоугольника вокруг выделенных объектов
        Pen selectRectPen;
        //фигура, у которой изменяется текст
        SolidFigure retextingFigure = null;
        //скролл-бары и квадрат, закрывающий угол между ними
        HScrollBar hsb = new HScrollBar();
        VScrollBar vsb = new VScrollBar();
        Label lab = new Label();

        public int SelectedFiguresCount
        {
            get { return selectedFigures.Count; }
        }
        //текущий инструмент
        protected DiagramBoxTools tool = DiagramBoxTools.Arrow;
        public DiagramBoxTools Tool
        {
            get { return tool; }
            set { tool = value; }
        }
        protected string filename = "";
        /// <summary>
        /// Файл, в который сохранена диаграмма
        /// </summary>
        public string FileName
        {
            get { return filename; }
            set { filename = value;}
        }
        //Курсор для добавления фигуры
        //*** Код преобразования изображения в курсор предоставлен пользователем ZokiManas ресурса Stackoverflow
        //http://stackoverflow.com/questions/516494/create-a-semi-transparent-cursor-from-an-image (дата обращения: 25 окт. 2013)***
        public struct IconInfo
        {
            public bool fIcon;
            public int xHotspot;
            public int yHotspot;
            public IntPtr hbmMask;
            public IntPtr hbmColor;
        }
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);

        [DllImport("user32.dll")]
        public static extern IntPtr CreateIconIndirect(ref IconInfo icon);
        
        public static Cursor CreateCursor(Bitmap bmp, int xHotSpot, int yHotSpot)
        {
            IntPtr ptr = bmp.GetHicon();
            IconInfo tmp = new IconInfo();
            GetIconInfo(ptr, ref tmp);
            tmp.xHotspot = xHotSpot;
            tmp.yHotspot = yHotSpot;
            tmp.fIcon = false;
            ptr = CreateIconIndirect(ref tmp);
            return new Cursor(ptr);
        }
        Cursor placeFigurePointer;
        //рисуемая прямая линия
        LineFigure drawingLine = null;
        public DiagramBox()
        {

            InitializeComponent();

            //создание дополнительного курсора
            Image image = (Image)Properties.Resources.icon_wand;
            Bitmap b = new Bitmap(image);
            placeFigurePointer = CreateCursor(b, 5, 5);
            
            //настройки вывода графики
            DoubleBuffered = true;
            ResizeRedraw = true;

            selectRectPen = new Pen(Color.BlueViolet, 1);
            selectRectPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
            //установка контекстного меню для объектов линий
            SetContextMenus();
            
            //добавление полос прокруток
            vsb.Name = "VSB1";
            hsb.Name = "HSB1";
            vsb.Visible = hsb.Visible = true;
            Controls.Add(hsb);
            hsb.Scroll += new ScrollEventHandler(hsb_Scroll);
            Controls.Add(vsb);
            vsb.Scroll += new ScrollEventHandler(vsb_Scroll);
            lab.Text = "";
            lab.BackColor = SystemColors.ButtonFace;
            Controls.Add(lab);   
        }
        
        //вычислить границы объектов диаграммы
        public void RecalcDiagramBorders()
        {
            diagramBorder = new RectangleF(Width / 2, Height / 2, 0, 0);
            foreach (Figure fig in diagram.figures)
            {
                    if ((fig.Bounds.Left < diagramBorder.Left) ||
                       (fig.Bounds.Right > diagramBorder.Right) ||
                       (fig.Bounds.Top < diagramBorder.Top) ||
                       (fig.Bounds.Bottom > diagramBorder.Bottom))
                    {
                        diagramBorder = RectangleF.Union(diagramBorder,
                            fig.Bounds);
                    }
            }
            //перестроить скролл-бары
            RebuildScrollBars();
        }
        protected void RebuildScrollBars()
        {
            int dM = 20;
            //делаем видимым горизонтальный скролл-бар, если нужно
            if ((diagramBorder.Left - dM < 0)
                || (diagramBorder.Right + dM > Width))
            {
                hsb.Location = new Point(0, Height - hsb.Height);
                hsb.Visible = true;
                float w1 = diagramBorder.Left - dM;
                float w2 = diagramBorder.Right + dM - Width;
                int dLeft = (int)(w1 < 0 ? w1 : 0);
                int dRight = (int)(w2 > 0 ? w2 : 0);
                hsb.Maximum = Width + dRight;
                hsb.Minimum = dLeft;
                hsb.LargeChange = Width;
                hsb.Value = 0;
            }
            else //прячем скролл-бар
            {
                hsb.Visible = false;
            }
            //делаем видимым вертикальный скролл-бар, если нужно
            if ((diagramBorder.Top - dM < 0)
                || (diagramBorder.Bottom + dM > Height))
            {
                vsb.Location = new Point(Width - vsb.Width, 0);
                vsb.Visible = true;
                float h1 = diagramBorder.Top - dM;
                float h2 = diagramBorder.Bottom + dM - Height;
                int dTop = (int)(h1 < 0 ? h1 : 0);
                int dBottom = (int)(h2 > 0 ? h2 : 0);
                vsb.Maximum = Height + dBottom;
                vsb.Minimum = dTop;
                vsb.LargeChange = Height;
                vsb.Value = 0;
            }
            else //прячем скролл-бар
            {
                vsb.Visible = false;
            }
            //перестраиваем ширину и высоту скролл-баров
            vsb.Height = Height - (hsb.Visible ? hsb.Height : 0);
            hsb.Width = Width - (vsb.Visible ? vsb.Width : 0);
            //модифицируем метку-заглушку
            if (hsb.Visible && vsb.Visible)
            {
                lab.Visible = true;
                lab.Width = vsb.Width;
                lab.Height = hsb.Height;
                lab.Location = new Point(hsb.Width, vsb.Height);
            }
            else
                lab.Visible = false;
        }
        //диаграмма, отображаемая компонентом
        public Diagram Diagram
        {
            get { return diagram; }
            set
            {
                diagram = value;
                draggedFigure = null;
                markers.Clear();
                Invalidate();
            }
        }
        //********************************************** ОТРИСОВКА ***************************
        protected override void OnPaint(PaintEventArgs e)
        {
            Draw(e.Graphics);
        }

        private void Draw(Graphics gr)
        {
            gr.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            selectRectPen.DashStyle = DashStyle.Dot;
            if (diagram == null)
                return;
            
            if (diagram != null)
            {
                //затем рисуем плоские и составные фигуры
                foreach (Figure f in diagram.figures)
                    if (f is IBackgroundFigure)
                        f.Draw(gr);
                //сначала рисуем соединительные линии
                foreach (Figure f in diagram.figures)
                    if (f is LineFigure)
                    {
                        f.Draw(gr);
                    }
                //затем рисуем плоские и составные фигуры
                foreach (Figure f in diagram.figures)
                    if (f is ISolidFigure && !(f is IBackgroundFigure))
                        f.Draw(gr);
            }

            //рисуем прямоугольники выделенных фигур
            foreach(Figure selectedFigure in selectedFigures)
            {
                if (selectedFigure is SolidFigure)
                {
                    SolidFigure figure = selectedFigure as SolidFigure;
                    RectangleF bounds = figure.Bounds;
                    gr.DrawRectangle(selectRectPen, bounds.Left - 2, bounds.Top - 2, bounds.Width + 4, bounds.Height + 4);
                }
                else if (selectedFigure is ComplexFigure)
                {
                    ComplexFigure figure = selectedFigure as ComplexFigure;
                    List<RectangleF> B = figure.SolidFiguresBounds;
                    for(int i = 0; i < B.Count; i++)
                    {
                        if (!(figure.SolidFigures[i] is PortFigure))
                        {
                            //все фигуры, кроме портов
                            RectangleF bounds = B[i];
                            gr.DrawRectangle(selectRectPen, bounds.Left - 2, bounds.Top - 2, bounds.Width + 4, bounds.Height + 4);
                        }
                        else //рисуем порты особым образом
                        {
                            PointF P = figure.SolidFigures[i].location;
                            gr.DrawRectangle(Pens.RoyalBlue, P.X - 3, P.Y - 3, 6, 6);
                        }
                    }
                }
            }
            //рисуем маркеры
            foreach (Marker m in markers)
                m.Draw(gr);
            //рисуем выделительную рамку
            if(IsSelectionWithFrame)
                gr.DrawRectangle(selectRectPen,
                    selectionFrame.X,
                    selectionFrame.Y,
                    selectionFrame.Width,
                    selectionFrame.Height);
            //рисуем рамку перетаскивания
            selectRectPen.DashStyle = DashStyle.Dash;
            if ( WasDraggedFrameMoved )
                gr.DrawRectangle(selectRectPen,
                    draggedFrame.X,
                    draggedFrame.Y,
                    draggedFrame.Width,
                    draggedFrame.Height);
            //выводим изображаемую линию
            if (drawingLine != null)
            {
                Pen p = new Pen(Brushes.Black);
                p.DashStyle = DashStyle.Dash;
                drawingLine.FigurePen = p;
                if(drawingLine.From != null && drawingLine.To != null)
                    drawingLine.Draw(gr);
            }
        }
        //********************************************** КОНЕЦ ОТРИСОВКИ ***************************
        public void UpdateConnectors()
        {
            //"гасим" все порты
            ComplexFigure CF;
            foreach (Figure f in diagram.figures)
            {
                if (f is ComplexFigure)
                {
                    CF = f as ComplexFigure;
                    foreach (Figure p in CF.SolidFigures)
                    {
                        if (p is IConnectable) (p as IConnectable).Disconnect();
                    }
                }
            }
            //"подключаем нужные порты"
            foreach (Figure f in diagram.figures)
                if (f is LineFigure)
                {
                    if ((f as LineFigure).From is IConnectable)
                        ((f as LineFigure).From as IConnectable).Connect();
                    if ((f as LineFigure).To is IConnectable)
                        ((f as LineFigure).To as IConnectable).Connect();
                }
        }

        

        //поиск фигуры, по данной точке
        Figure FindFigureByPoint(Point p)
        {
            //ищем среди маркеров
            foreach (Marker m in markers)
                if (m.IsInsidePoint(p))
                    return m;
            //затем ищем среди плоских фигур
            for (int i = diagram.figures.Count - 1; i >= 0; i--)
                if ((diagram.figures[i] is ISolidFigure) &&
                    !(diagram.figures[i] is IBackgroundFigure) &&
                    diagram.figures[i].IsInsidePoint(p))
                        return diagram.figures[i];
            //затем ищем среди линий
            for (int i = diagram.figures.Count - 1; i >= 0; i--)
                if (diagram.figures[i] is LineFigure && diagram.figures[i].IsInsidePoint(p))
                    return diagram.figures[i];
            //затем ищем среди фоновых фигур
            for (int i = diagram.figures.Count - 1; i >= 0; i--)
                if ((diagram.figures[i] is IBackgroundFigure) &&
                    diagram.figures[i].IsInsidePoint(p))
                    return diagram.figures[i];
            return null;
        }
        //определение фигур, попавших под рамку
        void SelectFiguresWithFrame()
        {
            foreach(Figure fig in diagram.figures)
            {
                //поиск среди еще не выделенных фигур
                if (!selectedFigures.Contains(fig))
                {
                    if (fig is SolidFigure)//среди сплошных фигур
                        if (selectionFrame.Contains((fig as SolidFigure).Bounds))
                        {
                            selectedFigures.Add(fig);
                            markers.AddRange(fig.CreateMarkers(diagram));
                        }
                    if (fig is ComplexFigure)//среди сплошных фигур
                        if (selectionFrame.Contains((fig as ComplexFigure).Bounds))
                        {
                            selectedFigures.Add(fig);
                            markers.AddRange(fig.CreateMarkers(diagram));
                        }
                    if (fig is LineFigure)//среди линий
                        if ((fig as LineFigure).IsLineInRect(selectionFrame))
                        {
                            selectedFigures.Add(fig);
                            markers.AddRange(fig.CreateMarkers(diagram));
                        }
                }
            }
        }
        //обновление рамки выделения
        public void UpdateSelectionFrame(Point CurrentPoint)
        {
            float W = CurrentPoint.X - startSelectionPoint.X;
            float H = CurrentPoint.Y - startSelectionPoint.Y;
            if (W < 0)//если двигаем мышь влево
            {
                selectionFrame.X = CurrentPoint.X;
                selectionFrame.Width = -W;
            }
            else
            {
                selectionFrame.X = startSelectionPoint.X;
                selectionFrame.Width = W;
            }
            if (H < 0)//двигаем мышью вверх
            {
                selectionFrame.Y = CurrentPoint.Y;
                selectionFrame.Height = -H;
            }
            else
            {
                selectionFrame.Y = startSelectionPoint.Y;
                selectionFrame.Height = H;
            }
        }
        private void AddFigure<FigureType>(Point location) where FigureType : SolidFigure
        {
            FigureType figure = Activator.CreateInstance<FigureType>();
            figure.Location = location;
            if (diagram != null)
            {
                
                //добавляем фигуру
                diagram.figures.Add(figure);
                //запоминаем текущее состояние
                memento.Memorize(diagram);
            }
            Invalidate();
        }
        private void AddComplexFigure<FigureType>(Point location) where FigureType : ComplexFigure
        {
            //создаем фигуру
            FigureType figure = Activator.CreateInstance<FigureType>();
            figure.Location = location;
            if (diagram != null)
            {
                if (!(figure is IBackgroundFigure))
                {
                    
                    //добавляем фигуру
                    diagram.figures.Add(figure);
                }
                else
                {
                    //поиск более раниих фоновых фигур
                    int i;
                    for (i = 0; i < diagram.figures.Count; i++)
                        if (!(diagram.figures[i] is IBackgroundFigure))
                            break;
                    diagram.figures.Insert(i, figure);
                }
                //запоминаем текущее состояние
                memento.Memorize(diagram);
            }
            //создаем логическую сущность данного блока
            AddBlockLogic(figure.GetType().Name, figure.Name, (figure as ComplexFigure).MainFigureText);
            Invalidate();
        }
        private void addRoundRectToolStripMenuItem_Click(Object sender, EventArgs e)
        {
            AddFigure<RoundRectFigure>(startDragPoint);
        }

        private void addRhombToolStripMenuItem_Click(Object sender, EventArgs e)
        {
            AddFigure<RhombFigure>(startDragPoint);
        }

        private void addParalelogrammToolStripMenuItem_Click(Object sender, EventArgs e)
        {
            AddFigure<ParalelogrammFigure>(startDragPoint);
        }

        private void addEllipseToolStripMenuItem_Click(Object sender, EventArgs e)
        {
            AddFigure<EllipseFigure>(startDragPoint);
        }
        protected void SetTextBox(SolidFigure figure, TextBox textBox)
        {
            textBox.Name = "FigureTextEdit";
            textBox.Parent = this;
            textBox.SetBounds(figure.TextBounds.Left, figure.TextBounds.Top, figure.TextBounds.Width, figure.TextBounds.Height);
            textBox.Text = figure.text;
            figure.text = "";
            if (figure.AutoSizeEnable)
                figure.AutoSize();
            textBox.Multiline = true;
            textBox.TextAlign = HorizontalAlignment.Center;
            textBox.Focus();
            textBox.LostFocus += new EventHandler(textBox_LostFocus);
            //
            retextingFigure = figure;

            figure.textFont = textBox.Font;
            //figure.textColor = new SolidBrush(textBox.ForeColor);
            
        }
        private void EditText(TextBox textBox)
        {
            if (selectedFigures.Count == 1)
            {
                //TextBox textBox = new TextBox();

                //FontDialog fontDialog = new FontDialog();
                //fontDialog.ShowColor = true;

                //if (fontDialog.ShowDialog() != DialogResult.Cancel)
                //{
                //    textBox.Font = fontDialog.Font;
                //    textBox.ForeColor = fontDialog.Color;
                //}

                if (selectedFigures[0] is SolidFigure &&
                    (selectedFigures[0] as SolidFigure).TextChangeEnable)
                {
                    SolidFigure figure = (selectedFigures[0] as SolidFigure);
                    SetTextBox(figure, textBox);
                }
                else if (selectedFigures[0] is ComplexFigure)
                {
                    ComplexFigure figure = (selectedFigures[0] as ComplexFigure);
                    //выбираем, в какой именно фигуре из доступных изменять текст
                    for (int i = 0; i < figure.SolidFigures.Count; i++)
                    {
                        if ((figure.SolidFigures[i].IsInsidePoint(startDragPoint))
                            && figure.SolidFigures[i].TextChangeEnable)
                        {
                            SolidFigure fig = figure.SolidFigures[i];
                            SetTextBox(fig, textBox);
                            selectedFigures.Clear();
                            selectedFigures.Add(fig);
                        }
                    }
                    //конец цикла
                }
                if (selectedFigures[0] is LabelFigure)
                {
                    textBox.TextChanged += new EventHandler(textBox_TextChanged);
                }
            }
        }

        void textBox_TextChanged(Object sender, EventArgs e)
        {
            //подгоним размер текст-бокса
            System.Drawing.Size S = new Size();
            S = TextRenderer.MeasureText(((Control)sender).Text,
                ((Control)sender).Font);
            S.Width += 20;
            S.Height += 10;
            ((Control)sender).Size = S;
            //выставим текст-бокс на центр изменяемой фигуры
            ((Control)sender).Location = new Point(
                (int)(retextingFigure.Location.X - ((Control)sender).Width / 2),
                (int)(retextingFigure.Location.Y - ((Control)sender).Height / 2));
        }

        void textBox_LostFocus(Object sender, EventArgs e)
        {
            retextingFigure.text = (sender as TextBox).Text;
            //Меняем текст в логике
            if(retextingFigure.OwnerFigure != null)
                ChangeText(retextingFigure.OwnerFigure.Name, retextingFigure.Text);
           //автосайз
            if (retextingFigure.AutoSizeEnable)
                retextingFigure.AutoSize();
            Controls.Remove((Control)sender);
            //записываем текущее состояние
            memento.Memorize(diagram);
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            if (e.KeyCode == Keys.ShiftKey)
                ShiftPressed = true;
            else
                TranslateHotKeys(e.KeyCode, e.Modifiers);
            UpdateMarkers();
            UpdateConnectors();
            Invalidate();
        }
        protected override void OnKeyUp(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.ShiftKey)
                ShiftPressed = false;
            base.OnKeyUp(e);
        }
        //экспорт в битмап
        public void SaveAsBitmap(string filename)
        {
            Bitmap bmp = FiguresToBitmap(diagram.figures);
            bmp.Save(filename);
        }
        public void SaveDiagram(FileStream fs, string filename)
        {
            //FileStream fs = new FileStream(filename, FileMode.Create);
            diagram.Save(fs);
            //присвоение имени диаграммы файлу
            FileName = filename;
        }
        public void OpenDiagram(FileStream fs, string filename)
        {
            // FileStream fs = new FileStream(filename, FileMode.Open);
            //загрузка диаграммы
            diagram = Diagram.Load(fs);
            //fs.Close();
            //установка имени файла
            FileName = filename;
            //перестройка изображения
            draggedFigure = null;
            selectedFigures.Clear();
            RecalcDiagramBorders();
            UpdateConnectors();
            Invalidate();
        }
        private void hScroller_Scroll(Object sender, ScrollEventArgs e)
        {
            int dx = e.NewValue - e.OldValue;
            foreach (Figure fig in diagram.figures)
                fig.Offset(-dx, 0);
            RecalcDiagramBorders();
            Invalidate();
        }

        private void DiagramBox_Resize(Object sender, EventArgs e)
        {
            if (diagram != null)
                RecalcDiagramBorders();
            else
            {
                //перестроить скролл-бары
                RebuildScrollBars();
            }
        }
        //сдвиг горизонтального скролл-бара
        void hsb_Scroll(Object sender, ScrollEventArgs e)
        {
            int dx = e.NewValue - e.OldValue;
            foreach (Figure fig in diagram.figures)
                fig.Offset(-(float)dx, 0);
            UpdateMarkers();
            Invalidate();
        }
        //сдвиг вертикального скролл-бара
        void vsb_Scroll(Object sender, ScrollEventArgs e)
        {
            int dy = e.NewValue - e.OldValue;
            foreach (Figure fig in diagram.figures)
                fig.Offset(0, -dy);
            UpdateMarkers();
            Invalidate();
        }
        private void DiagramBox_Load(Object sender, EventArgs e)
        {
            //сохранение нулевого состояния диаграммы
            memento.Memorize(diagram);
            //добавление возможности диаграм-боксу сообщать наружу об изменениях в нем
            memento.DiagramChange = DiagramChange;
        }
        /// <summary>
        /// Снять выделение со всех фигур диаграммы
        /// </summary>
        public void DeselectAll()
        {
            selectedFigures.Clear();
            markers.Clear();
            Invalidate();
        }
        /// <summary>
        /// Выбрать фигуру по ее индексу
        /// </summary>
        /// <param name="index"></param>
        public void SelectFigure(int index)
        {
            selectedFigures.Add(diagram.figures[index]);
            CreateMarkers();
            UpdateMarkers();
            Invalidate();
        }
    }
}